Skip to content

fix(engine): hide ffmpeg console windows on Windows - #3381

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/ffmpeg-windows-hide
Aug 21, 2026
Merged

fix(engine): hide ffmpeg console windows on Windows#3381
miguel-heygen merged 1 commit into
mainfrom
fix/ffmpeg-windows-hide

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

Pass windowsHide: true at every production ffmpeg/ffprobe spawn site.

Closes #3379.

Why

ffmpeg and ffprobe are console-subsystem binaries, and Node's child_process.spawn
defaults windowsHide to false. Every spawn therefore opens a visible console window on
Windows. A render shells out dozens of times across parallel workers, so the reporter saw a
burst of windows appear and disappear across their desktop, ~11 at once with 6 capture
workers.

Cosmetic, but it is the first impression of a render on Windows.

How

The report named two call sites. Seven production sites share the cause, so all seven
are fixed rather than the two mentioned:

file site
engine/src/utils/runFfmpeg.ts the shared runner, used across the pipeline
engine/src/utils/gpuEncoder.ts -encoders probe
engine/src/utils/gpuEncoder.ts per-encoder probe
engine/src/utils/ffprobe.ts metadata probe
engine/src/services/streamingEncoder.ts streaming encode
producer/src/services/audioExtractor.ts audio extraction
producer/src/services/distributed/shared.ts ffmpeg -version check

windowsHide is a no-op on macOS and Linux, so it is applied unconditionally.

Left alone deliberately: parity-harness.ts, regression-harness.ts and
mediaTypeTestFixtures.ts all spawn ffmpeg too, but they are dev-only tooling that never
runs on a user's desktop. Changing them would widen the diff without changing the reported
behaviour.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

Regression test

runFfmpeg.windowsHide.test.ts mocks child_process and asserts on the options object
actually handed to spawn:

const options = spawnMock.mock.calls[0]?.[2] as { windowsHide?: boolean } | undefined;
expect(options?.windowsHide).toBe(true);

Asserted on behaviour rather than on source text, so a future call site that drops the flag
is caught by what it does, not by how it is written.

Verified it fails without the fix:

$ # windowsHide removed from runFfmpeg
FAIL  runFfmpeg.windowsHide.test.ts > hides the console window so Windows renders do not flash terminals
$ # restored
Tests  1 passed (1)

Other checks

  • vitest packages/engine/src/utils/ — 651 passed, 20 files
  • oxlint + oxfmt on all 7 touched files — clean
  • Pre-commit hooks (fallow, typecheck, commitlint) — green

Before / after

I do not have a Windows machine, so I cannot capture the desktop screenshots this one
really wants. The observable change is at the spawn boundary, and that is what the test
pins:

before   spawn(getFfmpegBinary(), args)                          -> console window per call
after    spawn(getFfmpegBinary(), args, { windowsHide: true })   -> no window

The reporter's repro repo is linked in #3379 and is the right way to confirm the desktop
behaviour on a real Windows box before merge.

ffmpeg and ffprobe are console-subsystem binaries and Node defaults
windowsHide to false, so every spawn opened a visible console window on
Windows. A render shells out dozens of times across parallel workers,
which flashed a burst of windows across the user's desktop.

Applied at every production spawn site rather than only the two named in
the report, since they all share the cause: runFfmpeg, both gpuEncoder
probes, ffprobe, streamingEncoder, audioExtractor and the distributed
version check. windowsHide is a no-op on macOS and Linux.

The dev-only parity and regression harnesses are left alone; they never
run on a user's desktop.

Closes #3379
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Scope note, from a second Windows report that landed after this PR was opened.

This PR closes the ffmpeg/ffprobe half. It does not close the browser half, and I want
that on the record so the PR is not read as fixing the whole reported symptom.

one Windows Terminal window can open per cached chrome-headless-shell worker on Windows 11
... visible console windows use the cached HyperFrames chrome-headless-shell path as their
title. OUTCOME: render succeeds but interrupts the desktop.

Those windows come from Puppeteer's own launcher, not from any spawn in this repo.
@puppeteer/browsers@3.0.6 spawns the browser as:

this.#browserProcess = childProcess.spawn(this.#executablePath, this.#args, {
  detached: opts.detached,
  env,
  stdio,
});

No windowsHide, and the supported opts are only pipe, dumpio, handleSIGINT,
handleSIGTERM, handleSIGHUP, detached and env — there is no pass-through we could
set from ppt.launch(). Our launch sites in browserManager.ts and hdrCapture.ts have
nowhere to put the flag.

So the remaining half needs one of:

  1. an upstream change to @puppeteer/browsers to set windowsHide: true (or accept it as
    an option), or
  2. documenting the workaround the reporter found — point HYPERFRAMES_BROWSER_PATH at an
    installed GUI Chrome, which is not a console-subsystem binary.

Worth deciding which before closing the original issue, since a Windows user with this PR
merged will still see browser windows and reasonably think nothing changed.

@miguel-heygen
miguel-heygen merged commit 315a7b7 into main Aug 21, 2026
54 checks passed
@miguel-heygen
miguel-heygen deleted the fix/ffmpeg-windows-hide branch August 21, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows] ffmpeg subprocess windows flash/pop up during render, check, and snapshot (missing windowsHide)

1 participant